package com.xtiim.battery.discharge.protocal.manager;

import com.google.common.collect.Maps;
import com.xtiim.battery.discharge.protocal.command.CommandType;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class CommandMatcher {

    /**
     * <host, commandMatcher>
     */
    private static Map<String, CommandMatcher> commandMatcherMap = Maps.newConcurrentMap();

    public static CommandMatcher getInstance(String host) {
        CommandMatcher instance = commandMatcherMap.get(host);
        if (instance != null) {
            return instance;
        }

        synchronized (CommandMatcher.class) {
            return commandMatcherMap.computeIfAbsent(host, e -> new CommandMatcher());
        }
    }

    /**
     *  <     host     ><     cmd      >
     * | byte1 | byte2 | byte3 | byte4 |
     *
     */
    private volatile int filterMask = -1;

    private AtomicInteger filterMaskAutoResetCount = new AtomicInteger();

    private static final int autoResetCount = 10;

    /**
     * Match the given command
     *
     * @param host
     * @param cmdType
     * @return
     */
    public boolean match(String host, CommandType cmdType) {
        if (filterMask == -1) {
            return true;
        }
        //enum ack
        if (cmdType == CommandType.ACK) {
            return true;
        }

        //todo: hostNumber
        Integer hostNum = 1;

        // 0xFFFF means not restrict host. It is used in sending cmd to hosts one by one.
        synchronized (this) {
            if ((filterMask >>> 16) != 0xFFFF && (filterMask >>> 16) != hostNum) {
                autoReset(host);
                return false;
            }

            if (((filterMask & 0xFFFF) & cmdType.getMask()) == 0) {
                autoReset(host);
                return false;
            }
        }

        return true;
    }

    private void autoReset(String host) {
        int count = filterMaskAutoResetCount.incrementAndGet();
        if (count >= autoResetCount) {
            reset();
            filterMaskAutoResetCount.set(0);
            log.info("[Cmd_Mask_Reset], host: ====> {}, count: ====> {}", host, count);
        }
        log.info("commandMatcher match false, host: ====> {}, count: ====> {}", host, count);
    }

    /**
     * Config the matcher mask
     *
     * @param host
     * @param cmdTypes
     */
    public void config(String host, List<CommandType> cmdTypes) {
        //todo: hostNumber
        Integer hostNum = 1;

        synchronized (this) {
            int mask = 0;
            for (CommandType cmdType : cmdTypes) {
                mask |= cmdType.getMask();
            }

            mask |= (hostNum << 16);

            filterMask = mask;
        }

        log.info("[Cmd_Mask_Cfg]: {}", this);
    }

    /**
     * Reset to original value
     */
    public void reset() {
        synchronized (this) {
            filterMask = -1;
        }

        log.info("[Cmd_Mask_Reset]");
    }

    @Override
    public String toString() {
        return Integer.toBinaryString(filterMask);
    }

    public static void main(String[] args) {
//        CommandMatcher commandMatcher = CommandMatcher.getInstance();
//        commandMatcher.config("2", Lists.newArrayList(CommandType.RESUME_WORKFLOW, CommandType.CLEAR_DATA));

//        System.out.println(commandMatcher.match("2", CommandType.TIMING));
//        System.out.println(commandMatcher.match("2", CommandType.RESUME_WORKFLOW));
//        System.out.println(commandMatcher.match("1", CommandType.RESUME_WORKFLOW));
//        commandMatcher.reset();
//        System.out.println(commandMatcher.match("1", CommandType.RESUME_WORKFLOW));
//        System.out.println(commandMatcher.match("1", CommandType.COLLECT_RECORD));
//        System.out.println(commandMatcher.match("2", CommandType.CLEAR_WORKFLOW));
//
//        commandMatcher.config("1", Lists.newArrayList(CommandType.CAPACITY_THRESHOLD_SWITCH));
//        System.out.println(commandMatcher.match("1", CommandType.CAPACITY_THRESHOLD_SWITCH));
//        System.out.println(commandMatcher.match("1", CommandType.CURRENT_SETTING));

//        commandMatcher.config("2", Lists.newArrayList(CommandType.COLLECT_RECORD));
//        commandMatcher.config("4", Lists.newArrayList(CommandType.COLLECT_RECORD));
//        commandMatcher.config("4", Lists.newArrayList(CommandType.CURRENT_SETTING));
//        commandMatcher.config("2", Lists.newArrayList(CommandType.ACK));
//        System.out.println(commandMatcher.match("4", CommandType.ACK));

//        commandMatcher.config("2", Lists.newArrayList(CommandType.ACK));
//        System.out.println(commandMatcher.match("2", CommandType.ACK));
//        System.out.println(commandMatcher.match("1", CommandType.CAPACITY_THRESHOLD_SWITCH));
//
//        commandMatcher.config("2", Lists.newArrayList(CommandType.RESUME_WORKFLOW, CommandType.CLEAR_DATA));

//        CommandMatcher commandMatcher = CommandMatcher.getCommandMatcherMap().get("2");
//        System.out.println(commandMatcher);
//        CommandMatcher commandMatcher1 = new CommandMatcher("2");
//        System.out.println(commandMatcher1.hashCode());
//
//        CommandMatcher commandMatcher2 = CommandMatcher.getCommandMatcherMap().get("2");
//        System.out.println(commandMatcher2.hashCode());
//
//
//        commandMatcher2.config("2", Lists.newArrayList(CommandType.CAPACITY_THRESHOLD_SWITCH));
//        System.out.println(commandMatcher2.match("2", CommandType.COLLECT_RECORD));
//
//        System.out.println();
//        CommandMatcher commandMatcher3 = CommandMatcher.getCommandMatcherMap().get("3");
//        System.out.println(commandMatcher3);
//        CommandMatcher commandMatcher4 = new CommandMatcher("3");
//        System.out.println(commandMatcher4.hashCode());
//
//        CommandMatcher commandMatcher5 = CommandMatcher.getCommandMatcherMap().get("3");
//        System.out.println(commandMatcher5.hashCode());
//
//        commandMatcher5.config("3", Lists.newArrayList(CommandType.CAPACITY_THRESHOLD_SWITCH));
//        System.out.println(commandMatcher5.match("3", CommandType.COLLECT_RECORD));
//
//        System.out.println();
//        CommandMatcher commandMatcher6 = Optional.ofNullable(CommandMatcher.getCommandMatcherMap().get("4")).orElseGet(() -> new CommandMatcher("4"));
//        System.out.println(commandMatcher6);
//        System.out.println(commandMatcher6.hashCode());
//
//        CommandMatcher commandMatcher7 = Optional.ofNullable(CommandMatcher.getCommandMatcherMap().get("4")).orElseGet(() -> new CommandMatcher("4"));
//        System.out.println(commandMatcher7);
//        System.out.println(commandMatcher7.hashCode());

//        CommandMatcher instance = CommandMatcher.getInstance("2");
//        System.out.println(instance);
//        System.out.println(instance.hashCode());
//
//        CommandMatcher instance1 = CommandMatcher.getInstance("2");
//        System.out.println(instance1);
//        System.out.println(instance1.hashCode());
//
        new Thread(() -> {
            CommandMatcher instance = CommandMatcher.getInstance("2");
            System.out.println(Thread.currentThread().getName() + ": " + instance.hashCode());
            System.out.println(instance.hashCode());
            System.out.println(instance);

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "host-2").start();


        new Thread(() -> {
            CommandMatcher instance = CommandMatcher.getInstance("2");
            System.out.println(Thread.currentThread().getName() + ": " + instance.hashCode());
            System.out.println(instance.hashCode());
            System.out.println(instance);

        }, "host-2").start();


        new Thread(() -> {
            CommandMatcher instance = CommandMatcher.getInstance("3");
            System.out.println(Thread.currentThread().getName() + ": " + instance.hashCode());
            System.out.println(instance.hashCode());
            System.out.println(instance);

        }, "host-3").start();

        new Thread(() -> {
            CommandMatcher instance = CommandMatcher.getInstance("3");
            System.out.println(Thread.currentThread().getName() + ": " + instance.hashCode());
            System.out.println(instance.hashCode());
            System.out.println(instance);

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }, "host-3").start();
    }

}
